﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics;

namespace SimpleIR
{
	/// <summary>
	/// Class to represent primitive value types (ints, floats, bools, chars, etc.).
	/// </summary>
	public abstract class IRPrimitiveType : IRValueType
	{
		protected IRPrimitiveType(int typeid, string tnamespace, string tname, TypeQualifiers tqual)
			: base(typeid, tnamespace, tname, tqual)
		{ ;}

		public static bool IsT1AssignableToT2(IRPrimitiveType pt1, IRPrimitiveType pt2)
		{
			IRPrimitiveType t1 = (pt1 is IREnumType) ? ((IREnumType)pt1).UnerlyingNumericType : pt1;
			IRPrimitiveType t2 = (pt2 is IREnumType) ? ((IREnumType)pt2).UnerlyingNumericType : pt2;

			if(t1 == t2)
				return true;

			if(t1 is IREvalStackPrimitiveType)
			{
				if(t1 is IREvalStackTypeFloat64)
					return t2 is IRFloatingPointType;
				else
					return true;
			}
			else
			{
				if(t2 is IRSignedIntegerType & t1 is IRSignedIntegerType)
					return IRSignedIntegerType.IsT1AssignableToT2((IRSignedIntegerType)t1, (IRSignedIntegerType)t2);
				else if(t2 is IRUnSignedIntegerType & t1 is IRUnSignedIntegerType)
					return IRUnSignedIntegerType.IsT1AssignableToT2((IRUnSignedIntegerType)t1, (IRUnSignedIntegerType)t2);
				else if(t2 is IRFloatingPointType & t1 is IRFloatingPointType)
					return IRFloatingPointType.IsT1AssignableToT2((IRFloatingPointType)t1, (IRFloatingPointType)t2);
				else
					return false;
			}
		}
	}

	/// <summary>
	/// A simple class to represent all integer types.
	/// </summary>
	public class IRVoidType : IRPrimitiveType
	{
		public IRVoidType(int typeid)
			: base(typeid, "System", "Void", TypeQualifiers.PublicTag | TypeQualifiers.BuiltinTag)
		{ ;}
	}

	/// <summary>
	/// A base class for all the numeric eval stack types to inherit from.
	/// </summary>
	public abstract class IREvalStackPrimitiveType : IRPrimitiveType
	{
		public IREvalStackPrimitiveType(int typeid, string ename)
			: base(typeid, "System", ename, TypeQualifiers.PublicTag | TypeQualifiers.BuiltinTag)
		{ ;}
	}

	/// <summary>
	/// A simple class that represents 8/16/32 bit integers (unsigned or signed basically 32 bit blobs) on the eval stack.
	/// </summary>
	public sealed class IREvalStackTypeInt32 : IREvalStackPrimitiveType
	{
		public IREvalStackTypeInt32(int typeid)
			: base(typeid, "StackI32")
		{ ;}
	}

	/// <summary>
	/// A simple class that represents 64 bit integers (unsigned or signed basically 64 bit blobs) on the eval stack.
	/// </summary>
	public sealed class IREvalStackTypeInt64 : IREvalStackPrimitiveType
	{
		public IREvalStackTypeInt64(int typeid)
			: base(typeid, "StackI64")
		{ ;}
	}

	/// <summary>
	/// A simple class that the numeric types inherit.
	/// </summary>
	public sealed class IREvalStackTypeFloat64 : IREvalStackPrimitiveType
	{
		public IREvalStackTypeFloat64(int typeid)
			: base(typeid, "StackFloat")
		{ ;}
	}

	/// <summary>
	/// A class that all storage location numeric (ints, uints, and floats) types are subtypes of.
	/// </summary>
	public abstract class IRPrimitiveNumericType : IRPrimitiveType
	{
		public IRPrimitiveNumericType(int typeid, string pname)
			: base(typeid, "System", pname, TypeQualifiers.PublicTag | TypeQualifiers.BuiltinTag)
		{ ;}
	}

	/// <summary>
	/// A class that all storage location numeric (ints and uints) types are subtypes of.
	/// </summary>
	public abstract class IRPrimitiveIntegerType : IRPrimitiveNumericType
	{
		public IRPrimitiveIntegerType(int typeid, string pname)
			: base(typeid, pname)
		{ ;}
	}

	/// <summary>
	/// A simple class to represent all signed integer types.
	/// </summary>
	public sealed class IRSignedIntegerType : IRPrimitiveIntegerType
	{
		public enum SignedTypeTag { BoolTag, SByteTag, Int16Tag, Int32Tag, Int64Tag };

		public static string[] PrimNames = new string[] { "Boolean", "SByte", "Int16", "Int32", "Int64" };

		/// <summary>
		/// The exact type of the signed integer.
		/// </summary>
		public readonly SignedTypeTag SignedTag;

		public IRSignedIntegerType(int typeid, string tname)
			: base(typeid, tname)
		{
			if(tname.Equals("Boolean"))
				this.SignedTag = SignedTypeTag.BoolTag;
			else if(tname.Equals("SByte"))
				this.SignedTag = SignedTypeTag.SByteTag;
			else if(tname.Equals("Int16"))
				this.SignedTag = SignedTypeTag.Int16Tag;
			else if(tname.Equals("Int32"))
				this.SignedTag = SignedTypeTag.Int32Tag;
			else if(tname.Equals("Int64"))
				this.SignedTag = SignedTypeTag.Int64Tag;
			else
				throw new ArgumentException("Bad Prmitive Type Name: " + tname);
		} 

		public static bool IsT1AssignableToT2(IRSignedIntegerType pt1, IRSignedIntegerType pt2)
		{ return pt2.SignedTag - pt1.SignedTag >= 0; }
	}

	/// <summary>
	/// A simple class to represent all unsigned integer types.
	/// </summary>
	public sealed class IRUnSignedIntegerType : IRPrimitiveIntegerType
	{
		public enum UnsignedTypeTag { CharTag, ByteTag, UInt16Tag, UInt32Tag, UInt64Tag };

		public static string[] PrimNames = new string[] { "Byte", "Char", "UInt16", "UInt32", "UInt64" };

		/// <summary>
		/// The exact type of the unsigned integer.
		/// </summary>
		public readonly UnsignedTypeTag UnSignedTag;

		public IRUnSignedIntegerType(int typeid, string tname)
			: base(typeid, tname)
		{
			if(tname.Equals("Char"))
				this.UnSignedTag = UnsignedTypeTag.CharTag;
			else if(tname.Equals("Byte"))
				this.UnSignedTag = UnsignedTypeTag.ByteTag;
			else if(tname.Equals("UInt16"))
				this.UnSignedTag = UnsignedTypeTag.UInt16Tag;
			else if(tname.Equals("UInt32"))
				this.UnSignedTag = UnsignedTypeTag.UInt32Tag;
			else if(tname.Equals("UInt64"))
				this.UnSignedTag = UnsignedTypeTag.UInt64Tag;
			else
				throw new ArgumentException("Bad Prmitive Type Name: " + tname);
		}

		public static bool IsT1AssignableToT2(IRUnSignedIntegerType pt1, IRUnSignedIntegerType pt2)
		{
			if(pt2.UnSignedTag - pt1.UnSignedTag >= 0)
				return true;

			if(pt1.UnSignedTag == UnsignedTypeTag.CharTag)
				return (pt2.UnSignedTag == UnsignedTypeTag.CharTag) | (pt2.UnSignedTag == UnsignedTypeTag.ByteTag);
			else if(pt1.UnSignedTag == UnsignedTypeTag.ByteTag)
				return (pt2.UnSignedTag == UnsignedTypeTag.CharTag) | (pt2.UnSignedTag == UnsignedTypeTag.ByteTag);
			else
				return false;
		}
	}

	/// <summary>
	/// A simple class to represent all floating point types.
	/// </summary>
	public sealed class IRFloatingPointType : IRPrimitiveNumericType
	{
		public enum FloatingPointTypeTag { SingleTag, DoubleTag };

		public static string[] PrimNames = new string[] { "Single", "Double" };

		/// <summary>
		/// The exact type of the floating point type.
		/// </summary>
		public readonly FloatingPointTypeTag FloatingPointTag;

		public IRFloatingPointType(int typeid, string tname)
			: base(typeid, tname)
		{
			if(tname.Equals("Single"))
				this.FloatingPointTag = FloatingPointTypeTag.SingleTag;
			else if(tname.Equals("Double"))
				this.FloatingPointTag = FloatingPointTypeTag.DoubleTag;
			else
				throw new ArgumentException("Bad Prmitive Type Name: " + tname);
		}

		public static bool IsT1AssignableToT2(IRFloatingPointType pt1, IRFloatingPointType pt2)
		{ return pt2.FloatingPointTag - pt1.FloatingPointTag >= 0; }
	}

	/// <summary>
	/// A simple class to represent all function pointer types.
	/// </summary>
	public sealed class IRFunctionPointerType : IRPrimitiveType
	{
		public IRFunctionPointerType(int typeid, string tnamespace, string tbasename, TypeQualifiers tqual)
			: base(typeid, tnamespace, tbasename, tqual)
		{ ;}
	}

	/// <summary>
	/// A simple class to represent all enumerated types.
	/// </summary>
	public sealed class IREnumType : IRPrimitiveType
	{
		/// <summary>
		/// The underlying primitive type that backs this enumeration.
		/// </summary>
		public readonly IRPrimitiveIntegerType UnerlyingNumericType;

		public IREnumType(int typeid, string tnamespace, string tbasename, TypeQualifiers tqual, IRPrimitiveIntegerType underlyingtype)
			: base(typeid, tnamespace, tbasename, tqual)
		{ this.UnerlyingNumericType = underlyingtype; }
	}
}
